<?php

/* @(#) $Id: engine.php 4 2009-02-17 22:44:07Z mo@ilaven.net $ */

define('DBENGINE_UNKNOWN', 0);
define('DBENGINE_SQL', 1);
define('DBENGINE_LDAP', 2);

define('DBINDEX_NONE', null);
define('DBINDEX_PRIMARY', 1);
define('DBINDEX_UNIQUE', 2);
define('DBINDEX_KEY', 3);
define('DBINDEX_FULLTEXT', 4);

define('DBCREATE_NONE', 0);
define('DBCREATE_DROP', 1);
define('DBCREATE_NOTEXISTS', 2);

define('DBCHANGE_ADDCOL', 1);
define('DBCHANGE_DROPCOL', 2);
define('DBCHANGE_REPLACECOL', 3);
define('DBCHANGE_ADDINDEX', 4);
define('DBCHANGE_DROPINDEX', 5);

define('DBTYPE_ID', 1);
define('DBTYPE_INT', 2);
define('DBTYPE_CHAR', 3);
define('DBTYPE_VARCHAR', 4);
define('DBTYPE_TEXT', 5);
define('DBTYPE_LONGTEXT', 6);
define('DBTYPE_BLOB', 7);
define('DBTYPE_ENUM', 8);
define('DBTYPE_TIMESTAMP', 9);
define('DBTYPE_DATE', 10);
define('DBTYPE_TIME', 11);
define('DBTYPE_DATETIME', 12);
define('DBTYPE_TINYINT', 13);
define('DBTYPE_BOOLEAN', 14);
define('DBTYPE_FLOAT', 15);
define('DBTYPE_UUID', 16);
define('DBTYPE_GUID', DBTYPE_UUID);
define('DBTYPE_SET', 17);
define('DBTYPE_DECIMAL', 18);

define('DBFLAGS_NONE', 0);
define('DBFLAGS_NULL', 0);
define('DBFLAGS_PRIMARY', (1<<0));
define('DBFLAGS_SERIAL', (1<<1));
define('DBFLAGS_INDEX', (1<<2));
define('DBFLAGS_UNIQUE', (1<<3));
define('DBFLAGS_NOTNULL', (1<<4));
define('DBFLAGS_UNSIGNED', (1<<5));
define('DBFLAGS_FULLTEXT', (1<<6));

abstract class DBOEngine implements IPathfinderClass, IDatabaseEngine
{
	protected static $instances = array();
	protected $refCount = 1;

	public $moduleIdentifier;


	protected $persistKey;
	protected $persistEngine;
	
	protected $connectionClass = null;
	protected $shareConnections = false;
	protected $dbEngineType = DBENGINE_UNKNOWN;
	protected $connections = array();
	
	/* As of PHP 5.3, the following can be used: */

/*	public static function alloc()
	{
		$args = func_get_args();
		$inst = static::createInstanceArgs(null, $args);
		return $inst;
	} */

	public static function createInstance($className)
	{
		$args = func_get_args();
		array_shift($args);
		$inst = call_user_func(array($className, 'createInstanceArgs'), $className, $args);
		return $inst;
	}
	
	public static function createInstanceArgs($className, $args, $key = null, $engine = null, $holder = null)
	{
		if($className === null) $className == __CLASS__;
		if(!$holder && isset($args[0]) && is_object($args[0])) $holder = $args[0];
		if($key === null)
		{
			$key = $className;			
		}
		if(isset(self::$instances[$key]))
		{
			$inst = self::$instances[$key];
		}
		else
		{
			$reflect = new ReflectionClass($className);
			array_unshift($args, $engine); /* PersistEngine */
			array_unshift($args, $key); /* PersistKey */
			$inst = $reflect->newInstanceArgs($args);
		}
		$inst->retain($holder);
		return $inst;
	}
	
	public function __construct($persistKey, $persistEngine)
	{
		$this->moduleIdentifier = Pathfinder::getCurrentModule();
		$this->persistKey = $persistKey;
		$this->persistEngine = $persistEngine;
		$this->init();
	}
	
	public function retain($obj = null)
	{
		$this->refCount++;
		return $this;
	}

	public function release($obj = null)
	{
		$this->refCount--;
		if(!$this->refCount)
		{
			$this->dealloc();
		}
	}
	
	protected function init()
	{
	}
	
	protected function dealloc()
	{
	}

	public function connect($holder, $masterIRI, $slaveIRI = null)
	{
		if(!strlen($this->connectionClass))
		{
			throw new Exception('Cannot create a new connection; connectionClass is empty');
		}
		$inst = null;
		$masterConKey = $this->getConnectionKey($masterIRI);
		$slaveConKey = $this->getConnectionKey($slaveIRI);
		$key = $this->connectionClass . '/[' . $masterConKey . ']';
		if(strlen($slaveConKey)) $key .= '['. $slaveConKey . ']';
		$inst = call_user_func_array(array($this->connectionClass, 'createInstanceArgs'),
			array($this->connectionClass, array($this, $masterIRI, $slaveIRI),
			$key, null, $this));
		$db = $inst->getDatabase($holder, $masterIRI, $slaveIRI);
		$inst->release($this);
		return $db;
	}
	
	/* This can be overridden by descendants to allow multiple IRIs to be
	 * given the same key to share connections between multiple databases,
	 * typically where the same host, user and password are given (but the
	 * name of the database itself may differ).
	 */
	protected function getConnectionKey($iri)
	{
		if($iri === null) return null;
		$info = parse_url($iri);
		$key = $info['scheme'] . '://';
		if(isset($info['user']))
		{
			$key .= $info['user'];
			if(isset($info['pass'])) $key .= ':' . $info['pass'];
			$key .= '@';
		}
		if(isset($info['host']))
		{
			$key .= $info['host'];
		}
		else
		{
			$key .= 'localhost';
		}
		return $key;
	}
	
	public function __get($name)
	{
		throw new Exception('Undefined or hidden property ' . get_class($this) . '::' . $name);
	}
}
